%%%-------------------------------------------------------------------
%%% @author liuwentao
%%% @doc
%%%     单位管理
%%% @end
%%% Created : 26. 7月 2021 15:49
%%%-------------------------------------------------------------------
-module(unit_mgr).
-author("liuwentao").

-behaviour(gen_server).


%% 非进程接口
-export([
    info/1                        %% 查询单位信息
    , fetch_ids/1                   %% 获取唯一id
    , create/1                      %% 创建单位
    , create/4                      %% 创建单位
    , create/5                      %% 创建单位
    , create/6                      %% 创建单位
    , create/8                      %% 创建单位
    , remove/1                      %% 移除单位
    , remove/2                      %% 移除单位
    , list/2                        %% 获取地图单位列表
    , get_unit_data/2               %% 获取单位配置
    , insert/1                      %% 保存单位信息
]).

%% 进程接口
-export([
    start_link/0        %% 进程开启函数
    , loop_daily/0      %% 0点回调
]).


%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-define(SERVER, ?MODULE).
-include("common.hrl").
-include("unit.hrl").
-include("pos.hrl").

%% 动态单位的id从1000000起
-record(state, {
    next_id = 1000000
}
).

%%%===================================================================
%%% API
%%%===================================================================
%% @doc 从本服的单位管理器获取多个新单位的ID
-spec fetch_ids(pos_integer()) -> {ok, list()} | {error, term()}.
fetch_ids(Num) ->
    ?CALL(?MODULE, {fetch_ids, Num}).


%% ----------------------------------------------------
%% 创建单位
%% ----------------------------------------------------
create(UnitId, Base, UnitType, Pos) ->
    create(UnitId, Base, UnitType, Pos, [], "").
create(UnitId, Base, UnitType, Pos, {evt, Evts}) ->
    create(UnitId, Base, UnitType, Pos, Evts, "");
create(UnitId, Base, UnitType, Pos, {disable, Disabled}) ->
    create(UnitId, Base, UnitType, Pos, [], Disabled).

%% 战场-固定ID
create({BattleId, Id}, UnitBase = #unit_data{activity = ?true}, _UnitType, Pos, Evts, Disabled) ->
    {ok, Unit} = unit_convert:to({unit, Pos, Evts, Disabled}, UnitBase),
    unit:create({BattleId, Id}, Unit);
create({BattleId, Id}, UnitBase = #unit_data{activity = ?false}, _UnitType, Pos, Evts, Disabled) ->
    {ok, Unit} = unit_convert:to({unit, Pos, Evts, Disabled}, UnitBase),
    create({BattleId, Id}, Unit);
create({BattleId, Id}, BaseId, UnitType, Pos, Evts, Disabled) ->
    create(#{id => {BattleId, Id}, type => UnitType, base_id => BaseId, pos => Pos, evts => Evts, disabled => Disabled});
%% 战场-动态ID
create(BattleId, UnitBase = #unit_data{activity = ?false}, _UnitType, Pos, Evts, Disabled) ->
    case fetch_ids(1) of
        {error, Reason} -> {error, Reason};
        {ok, [Id]} ->
            {ok, Unit} = unit_convert:to({unit, Pos, Evts, Disabled}, UnitBase),
            create({BattleId, Id}, Unit)
    end;
create(BattleId, UnitBase = #unit_data{activity = ?true}, _UnitType, Pos, Evts, Disabled) ->
    case fetch_ids(1) of
        {error, Reason} -> {error, Reason};
        {ok, [Id]} ->
            {ok, Unit} = unit_convert:to({unit, Pos, Evts, Disabled}, UnitBase),
            unit:create({BattleId, Id}, Unit)
    end;
create(BattleId, BaseId, UnitType, Pos, Evts, Disabled) when is_integer(BattleId) ->
    create(#{battle_id => BattleId, type => UnitType, base_id => BaseId, pos => Pos, evts => Evts, disabled => Disabled});

create({BattleId, Id}, BaseId, Type, Pos, Evts, Disabled) ->
    create(#{id => {BattleId, Id}, base_id => BaseId, pos => Pos, evts => Evts, disabled => Disabled, type => Type}).

create(BattleId, UnitBase = #unit_data{activity = ?true}, _UnitType, Pos, Evts, Disabled, Path, Points) ->
    case fetch_ids(1) of
        {error, Reason} -> {error, Reason};
        {ok, [Id]} ->
            {ok, Unit} = unit_convert:to({unit, Pos, Evts, Disabled, Path, Points}, UnitBase),
            unit:create({BattleId, Id}, Unit)
    end.

%% 固定id
create(Args = #{id := {BattleId, Id}, type := Type, base_id := BaseId, pos := Pos}) ->
    Evts = maps:get(evts, Args, []),
    Disabled = maps:get(disabled, Args, []),
    Prop = maps:get(prop, Args, []),
    case get_unit_data(Type, BaseId) of
        {error, Reason} -> {error, Reason};
        UnitBase = #unit_data{activity = ?false} ->
            {ok, Unit} = unit_convert:to({unit, Pos, Evts, Disabled}, UnitBase),
            create({BattleId, Id}, Unit#unit{prop = Prop});
        UnitBase = #unit_data{activity = ?true} ->
            {ok, Unit} = unit_convert:to({unit, Pos, Evts, Disabled}, UnitBase),
            unit:create({BattleId, Id}, Unit#unit{prop = Prop})
    end;
%% 动态id
create(Args = #{battle_id := BattleId, type := Type, base_id := BaseId, pos := Pos}) ->
    Evts = maps:get(evts, Args, []),
    Disabled = maps:get(disabled, Args, []),
    Prop = maps:get(prop, Args, []),
    case get_unit_data(Type, BaseId) of
        {error, Reason} -> {error, Reason};
        UnitBase = #unit_data{activity = ?false} ->
            {ok, Unit} = unit_convert:to({unit, Pos, Evts, Disabled}, UnitBase),
            case fetch_ids(1) of
                {error, Reason} -> {error, Reason};
                {ok, [Id]} -> create({BattleId, Id}, Unit#unit{prop = Prop})
            end;
        UnitBase = #unit_data{activity = ?true} ->
            {ok, Unit} = unit_convert:to({unit, Pos, Evts, Disabled}, UnitBase),
            case fetch_ids(1) of
                {error, Reason} -> {error, Reason};
                {ok, [Id]} -> unit:create({BattleId, Id}, Unit#unit{prop = Prop})
            end
    end.

%% 非进程单位创建, 进程单位需要在unit:create
create(Id, Unit = #unit{base_id = _BaseId, pos = Pos = #pos{map = MapId}}) ->
    U = Unit#unit{id = Id},
    {ok, MapUnit} = unit_convert:to(map_unit, U),
    %% 进入地图
    case map:unit_enter(MapId, MapUnit) of
        error ->
            ?ERR("单位[~w]进入地图[~w]失败", [_BaseId, MapId]),
            {error, enter_map_failed};
        {ok, MapBaseId, MapPid, GridPid} ->
            NewU = U#unit{pos = Pos#pos{map_base_id = MapBaseId, map_pid = MapPid, map_grid_pid = GridPid}},
            catch ets:insert(?unit_info, NewU),
            {ok, NewU}
    end.


%% @doc 移除单位
remove(MapId, BaseId) ->
    List = list(MapId, BaseId),
    unit_mgr:remove(List).

%% @doc 保存单位信息
insert(State) ->
    catch ets:insert(?unit_info, State).



%% @doc 根据指定的单位ID删除地图单位
-spec remove(Unit :: tuple() | #unit{} | [tuple() | #unit{}]) -> ok.
remove([]) -> ok;
remove([I | T]) ->
    remove(I),
    remove(T);


remove(#unit{pid = Pid}) when is_pid(Pid) ->
    unit:stop(Pid),
    ok;
remove(#unit{pos = #pos{map_pid = 0}}) ->     %% 所在场景pid有误（一般为剧情单位伪造的#unit{}）
    ok;
remove(#unit{id = UnitId, pos = #pos{map_pid = MapPid}}) ->
    %% 非活动单位
    map:unit_leave(MapPid, UnitId),
    catch ets:delete(?unit_info, UnitId),
    ok;
remove({UnitId, Plat, ZoneId}) when is_list(Plat) ->
    case info({Plat, ZoneId, UnitId}) of
        {ok, #unit{pid = Pid}} when is_pid(Pid) ->
            %% 活动单位，结束进程
            unit:stop(Pid);
        {ok, #unit{pos = #pos{map_pid = MapPid}}} ->
            %% 非活动单位
            map:unit_leave(MapPid, UnitId),
            catch ets:delete(?unit_info, UnitId);
        _E ->
            ?ERR("无法删除的地图单位：~w ~w ~w", [UnitId, _E, util:get_stacktrace()])
    end,
    ok;
remove(UnitId) ->
    case info(UnitId) of
        {ok, #unit{pid = Pid}} when is_pid(Pid) ->
            %% 活动单位，结束进程
            unit:stop(Pid);
        {ok, #unit{pos = #pos{map_pid = MapPid}}} ->
            %% 非活动单位
            map:unit_leave(MapPid, UnitId),
            catch ets:delete(?unit_info, UnitId);
        _E ->
            ?ERR("无法删除的地图单位：~w ~w ~w", [UnitId, _E, util:get_stacktrace()])
    end,
    ok.

%% ----------------------------------------------------
%% 查询单位
%% ----------------------------------------------------
-spec info(UnitId) -> {ok, #unit{}} | {error, not_found} when
    UnitId :: {pos_integer(), pos_integer()} | pos_integer().
info(UnitId) ->
    case catch ets:lookup(?unit_info, UnitId) of
        [U] -> {ok, U};
        _ -> {error, not_found}
    end.


%% @doc 0点回调
-spec loop_daily() -> ok.
loop_daily() ->
    info(loop_daily).

%% @doc 进程启动
-spec(start_link() ->
    {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
    ?INFO("[~w] 正在启动...", [?MODULE]),
    process_flag(trap_exit, true),
    ets:new(?unit_info, [set, named_table, public, {keypos, #unit.id}]),
    ?INFO("[~w] 启动完成...", [?MODULE]),
    {ok, #state{}}.


%% 执行同步apply操作
handle_call({apply_sync, {F}}, _From, State) ->
    handle_apply_sync_return(catch erlang:apply(F, [State]), {undefined, F, []}, State);
handle_call({apply_sync, {F, A}}, _From, State) ->
    handle_apply_sync_return(catch erlang:apply(F, [State | A]), {undefined, F, A}, State);
handle_call({apply_sync, {M, F, A}}, _From, State) ->
    handle_apply_sync_return(catch erlang:apply(M, F, [State | A]), {M, F, A}, State);


%% 获取单位的id
handle_call({fetch_ids, Num}, _From, State = #state{next_id = NextId}) ->
    {reply, {ok, lists:seq(NextId, (NextId + Num - 1))}, State#state{next_id = NextId + Num}};

handle_call(_Request, _From, State) ->
    {reply, ok, State}.

handle_cast(_Request, State) ->
    {noreply, State}.


%% 执行异步apply操作
handle_info({apply_async, {F}}, State) ->
    handle_apply_async_return(catch erlang:apply(F, [State]), {undefined, F, []}, State);
handle_info({apply_async, {F, A}}, State) ->
    handle_apply_async_return(catch erlang:apply(F, [State | A]), {undefined, F, A}, State);
handle_info({apply_async, {M, F, A}}, State) ->
    handle_apply_async_return(catch erlang:apply(M, F, [State | A]), {M, F, A}, State);

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%%===================================================================
%%% 工具函数
%%%===================================================================
%% 处理同步apply的返回值
handle_apply_sync_return({ok, Reply, NewState = #state{}}, _Mfa, _State) ->
    {reply, Reply, NewState};
handle_apply_sync_return({ok, Reply}, _Mfa, State) ->
    {reply, Reply, State};
handle_apply_sync_return({stop, Reason, NewState = #state{}}, _Mfa, _State) ->
    {stop, Reason, NewState};
handle_apply_sync_return({stop, Reason, Reply, NewState = #state{}}, _Mfa, _State) ->
    {stop, Reason, Reply, NewState};
handle_apply_sync_return(Else, {M, F, A}, State) ->
    ?ERR("同步执行{~w, ~w, ~w}时得到错误的返回值格式:~w", [M, F, A, Else]),
    {reply, Else, State}.


%% 处理异步apply的返回值
handle_apply_async_return({ok, NewState = #state{}}, _Mfa, _State) ->
    {noreply, NewState};
handle_apply_async_return(ok, _Mfa, State) ->
    {noreply, State};
handle_apply_async_return({stop, Reason, NewState = #state{}}, _Mfa, _State) ->
    {stop, Reason, NewState};
handle_apply_async_return(Else, {M, F, A}, State) ->
    ?ERR("执行{~w, ~w, ~w}时得到错误的返回值格式:~w", [M, F, A, Else]),
    {noreply, State}.


%% @doc 查找指定地图上固定单位
-spec list(MapId :: non_neg_integer(), UnitBaseId :: non_neg_integer()) -> [#unit{}].
list(MapId, UnitBaseId) ->
    case catch ets:match_object(?unit_info, #unit{base_id = UnitBaseId, pos = #pos{map = MapId, _ = '_'}, _ = '_'}) of
        L = [_ | _] -> L;
        _Err ->
            []
    end.


%% @doc 获取单位配置数据
get_unit_data(npc, Bid) ->
    get_unit_data(?unit_type_npc, Bid);
get_unit_data(elem, Bid) ->
    get_unit_data(?unit_type_elem, Bid);
get_unit_data(mon, Bid) ->
    get_unit_data(?unit_type_mon, Bid);

get_unit_data(?unit_type_elem, Bid) ->
    elem_data:get(Bid);
get_unit_data(?unit_type_npc, Bid) ->
    npc_data:get(Bid);
get_unit_data(?unit_type_mon, Bid) ->
    case mon_data:get_mon(Bid) of
        UnitData = #unit_data{} ->
            SlaveList = mon_data:get_slaves(Bid),
            UnitData#unit_data{slave = SlaveList};
        Err -> Err
    end;

%% @doc 查询单位数据
get_unit_data(unit, Bid) ->
    get_every_unit_data([elem, mon, npc], Bid).
get_every_unit_data([], Bid) ->
    elem_data:get(Bid);
get_every_unit_data([H | T], Bid) ->
    case get_unit_data(H, Bid) of
        U = #unit_data{} ->
            U;
        _ ->
            get_every_unit_data(T, Bid)
    end.